﻿using Furion.CMS.Domain;
using Furion.DatabaseAccessor;
using Furion.DataEncryption;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
using Microsoft.EntityFrameworkCore;
using Mapster;
using Furion.CMS.Application.Dto;
using Furion.CMS.Core.Base;
using Microsoft.Extensions.Primitives;
using Furion.CMS.Core;
using Microsoft.Extensions.Caching.Distributed;
using Furion.FriendlyException;
using Furion.DependencyInjection;
using Furion.CMS.Web.Core;
using Furion.CMS.Application;
using Hei.Captcha;

namespace Furion.CMS.Web.Controllers
{
    /// <summary>
    /// 账号相关
    /// </summary>
    [ApiController]
    [Route("api")]
    [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
    public class AccountController : ControllerBase
    {
        private readonly IRepository<UserInfo> _userRepository;
        private readonly IRepository<UserLoginRecord> _loginRecordRepository;
        private readonly IRepository<RoleInfo> _roleRepository;

        private readonly Func<string, ITransient, object> LoginHandlerResolve;
        private readonly IDistributedCache<IEnumerable<string>> PermissionCache;
        private readonly IDistributedCache<string> _captchaCache;
        private readonly IDistributedCache<Dictionary<string, string>> _jwtTokenCache;
        private readonly IDistributedCache<long> _sessionCaches;
        private readonly SecurityCodeHelper _securityCode;
        private readonly SiteSettingAppService _settingAppService;

        /// <summary>
        /// 构造函数
        /// </summary>
        public AccountController(IRepository<UserInfo> userRepository,
            IRepository<UserLoginRecord> loginRecordRepository,
            IRepository<RoleInfo> roleRepository,
            Func<string, ITransient, object> loginHandlerResolve,
            IDistributedCache<IEnumerable<string>> permissionCache,
            IDistributedCache<string> captchaCache,
            IDistributedCache<Dictionary<string, string>> jwtTokenCache,
            IDistributedCache<long> sessionCaches,
            SecurityCodeHelper securityCode,
            SiteSettingAppService settingAppService)
        {
            _userRepository = userRepository;
            _loginRecordRepository = loginRecordRepository;
            _roleRepository = roleRepository;

            LoginHandlerResolve = loginHandlerResolve;
            PermissionCache = permissionCache;
            _captchaCache = captchaCache;
            _jwtTokenCache = jwtTokenCache;
            _sessionCaches = sessionCaches;
            _securityCode = securityCode;
            _settingAppService = settingAppService;
        }

        /// <summary>
        /// 账号登录验证
        /// </summary>
        /// <param name="model">登录参数</param>
        /// <returns></returns>
        /// <remarks>
        /// Sample request:
        ///
        ///     Account
        ///     {
        ///        "username": "admin",
        ///        "password": "21232f297a57a5a743894a0e4a801fc3",
        ///        "type": "account",
        ///        "autoLogin": true
        ///     }
        ///     
        ///     Mobile
        ///     {
        ///        "mobile": "18328408303",
        ///        "captcha": "1234",
        ///        "type": "mobile",
        ///        "autoLogin": true
        ///     }
        ///
        /// </remarks>
        [HttpPost("login/account")]
        [AllowAnonymous]
        [ProducesResponseType(typeof(SimpleResult<LoginResult>), 200)]
        public async Task<IActionResult> LoginVerifyAsync([FromBody] LoginParams model)
        {
            var flag = LoginVerifyType.TryFromNamed(model.Type, out LoginVerifyType LoginHandler);

            var handler = LoginHandlerResolve(model.Type, default) as ILoginHandler;

            // TODO:可考虑将登录记录标签化
            var dbLogging = model.ToLoginRecord(HttpContext);

            // 返回消息体
            var response = new SimpleResult<LoginResult>
            {
                Success = true,
                Data = new LoginResult(model.Type, LoginStatus.Error)
            };

            try
            {
                if (!flag) throw new AccountLoginException("参数错误");

                var settings = _settingAppService.GetTenantSettings();

                var personPassport = await handler?.VerifyAsync(model.GetAll(), dbLogging, settings);

                // 判断通行证关联用户是否正常
                if (!(personPassport.User?.Enable ?? false))
                    throw new AccountLoginException("用户已被停用,请与管理员联系.", "用户已被停用");

                if (!personPassport.Enable)
                    throw new AccountLoginException("账号已被停用,请使用其他方式登录.", "普通账号已被停用");
                // 创建JWT Token
                var jwtToken = CreateJwtToken(personPassport, model.AutoLogin);

                // 更新登录日志
                dbLogging.IsSuccess = true;
                dbLogging.ErrorMessage = "";

                /*
                 * 启用了双因素验证,只有在使用账号登录时才会生效
                 * 若账号未设置手机号则跳过双因素验证处理
                 */
                if (settings.IsTwoFactorVerify && LoginHandler == LoginVerifyType.Account
                    && !string.IsNullOrWhiteSpace(personPassport.User?.PhoneNumber))
                {
                    var phoneNumber = personPassport.User.PhoneNumber;
                    var tokenKey = $"TwoFactorVerify_{phoneNumber}";

                    await CreateCaptcha(phoneNumber, "towFactor");
                    // 双因素验证缓存的token只保留60s
                    _jwtTokenCache.GetOrAdd(tokenKey, () => jwtToken,
                        new DistributedCacheEntryOptions { AbsoluteExpiration = DateTimeOffset.UtcNow.AddSeconds(60) });

                    response.Data.OnTwoFactorVerify(personPassport.User.PhoneNumber);
                }
                else
                {
                    // 将jwt添加到返回头中
                    foreach (var item in jwtToken)
                    {
                        HttpContext.Response.Headers[item.Key] = item.Value;
                    }

                    response.Data.OnSuccess();
                }
            }
            catch (AccountLoginException ex)
            {
                dbLogging.IsSuccess = false;
                dbLogging.ErrorMessage = (ex.InnerException ?? ex).Message;
                response.Data.OnError(ex.Message);
            }
            catch (Exception innerException)
            {
                dbLogging.IsSuccess = false;
                dbLogging.ErrorMessage = "登录处理程序出现异常";
                dbLogging.InnerException = new InnerExceptionRecord(innerException.Message);
                throw;
            }
            finally
            {
                // 写登录日志
                await _loginRecordRepository.InsertAsync(dbLogging);
            }
            return Ok(response);
        }

        /// <summary>
        /// 创建JwtToken
        /// </summary>
        /// <param name="passport"></param>
        /// <param name="autoLogin"></param>
        /// <returns></returns>
        private Dictionary<string, string> CreateJwtToken(UserPassport passport, bool autoLogin)
        {
            var result = new Dictionary<string, string>();
            // 创建JWT token有效载荷数据
            var payload = new Dictionary<string, object>();
            payload.Add(AppClaimConsts.UserId, passport.UserId);
            payload.Add(AppClaimConsts.Account, passport.Account);
            payload.Add(AppClaimConsts.AutoLogin, autoLogin); // 决定了是否创建刷新token

            var sessionId = (passport.UpdatedTime ?? passport.CreatedTime).ToLong();
            payload.Add(AppClaimConsts.SessionId, sessionId);
            // 创建会话缓存(有效时间12小时)
            var expire = DateTime.Now.AddHours(12) - DateTime.Now;
            _sessionCaches.Set(string.Format(CacheKeyConsts.SessionSafetyIdentify, passport.UserId), sessionId,
                new DistributedCacheEntryOptions
                {
                    AbsoluteExpirationRelativeToNow = expire
                });

            payload.Add(AppClaimConsts.TenantId, passport.TenantId);
            // 合并组织机构角色与人员角色
            var orgRoles = passport.User.Organizations.SelectMany(s => s.Roles);
            var allRoles = passport.User.Roles.UnionBy(orgRoles, x => x.Code);
            payload.Add(AppClaimConsts.RoleIdList, string.Join(",", allRoles.Select(s => s.Code)));
            // 添加人员机构
            payload.Add(AppClaimConsts.OrgIdList, string.Join(",", passport.User.Organizations.Select(s => s.Id)));

            // 生成JWT token
            var accessToken = JWTEncryption.Encrypt(payload);
            result.Add("access-token", accessToken);
            if (autoLogin)
            {
                // 刷新token有效期为12个小时
                var refreshToken = JWTEncryption.GenerateRefreshToken(accessToken, 43200);
                result.Add("x-access-token", refreshToken);
            }
            return result;
        }

        /// <summary>
        /// 双因素验证
        /// </summary>
        /// <param name="verifyParams"></param>
        /// <returns></returns>
        /// <exception cref="AccountLoginException"></exception>
        [HttpPost("login/account/two-factor-verify")]
        [AllowAnonymous]
        [ProducesResponseType(typeof(NotDataResult), 200)]
        public async Task<IActionResult> TwoFactorVerifyAsync([FromBody] TwoFactorVerifyParams verifyParams)
        {
            var cacheCode = await _captchaCache.GetAsync($"captcha_{verifyParams.PhoneNumber}_towFactor");
            if (!cacheCode?.Equals(verifyParams.Captcha, StringComparison.OrdinalIgnoreCase) ?? true)
            {
                throw new AccountLoginException("验证码失效或输入错误");
            }
            // 验证通过后获取token
            var tokenCacheKey = $"TwoFactorVerify_{verifyParams.PhoneNumber}";
            var tokenValue = await _jwtTokenCache.GetAsync(tokenCacheKey);

            if (tokenValue == null)
                throw new AccountLoginException("系统异常,请刷新页面后重试");

            // 设置响应头
            foreach (var token in tokenValue)
            {
                HttpContext.Response.Headers[token.Key] = token.Value;
            }
            await _jwtTokenCache.RemoveAsync(tokenCacheKey);

            return Ok(new NotDataResult { Success = true });
        }

        /// <summary>
        /// 获取验证码
        /// </summary>
        /// <param name="phone"></param>
        /// <returns></returns>
        [HttpGet("captcha")]
        [AllowAnonymous]
        [ProducesResponseType(typeof(NotDataResult), 200)]
        public async Task<IActionResult> GetCaptchaAsync(string phone)
        {
            await CreateCaptcha(phone);

            return Ok(new NotDataResult { Success = true });
        }

        /// <summary>
        /// 创建验证码
        /// </summary>
        /// <param name="phone"></param>
        /// <param name="after"></param>
        /// <returns></returns>
        private async Task CreateCaptcha(string phone, string after = default)
        {
            var settings = _settingAppService.GetTenantSettings();
            // TODO:暂未介入短信
            if (!string.IsNullOrWhiteSpace(after)) after = $"_{after}"; // 追加的格式处理

            await _captchaCache.GetOrAddAsync($"captcha_{phone}{after}", () => Task.FromResult("1234"),
                new DistributedCacheEntryOptions { AbsoluteExpiration = DateTimeOffset.UtcNow.AddSeconds(settings.CaptchaDuration) });
        }

        /// <summary>
        /// 获取图形验证码
        /// </summary>
        /// <returns></returns>
        [HttpGet("captcha/graphic")]
        [AllowAnonymous]
        [ApiDescriptionSettings(false)]
        public async Task<IActionResult> GetGraphicCaptchaAsync(string timestamp)
        {
            var settings = _settingAppService.GetTenantSettings();
            var code = _securityCode.GetRandomEnDigitalText(4);
            await _captchaCache.GetOrAddAsync($"captcha_{timestamp}", () => Task.FromResult(code),
                 new DistributedCacheEntryOptions { AbsoluteExpiration = DateTimeOffset.UtcNow.AddSeconds(settings.CaptchaDuration) });
            // 生成验证码
            var imgbyte = _securityCode.GetGifEnDigitalCodeByte(code);
            return File(imgbyte, "image/gif");
        }

        /// <summary>
        /// 刷新当前登录用户
        /// </summary>
        /// <returns></returns>
        [HttpGet("currentUser/refresh")]
        [ProducesResponseType(typeof(NotDataResult), 200)]
        public async Task<IActionResult> RefreshCurrentUserAsync([FromServices] IRepository<UserPassport> passportRepository)
        {
            // 从token中获取关键信息
            var account = User.FindFirstValue(AppClaimConsts.Account);
            bool.TryParse(User.FindFirstValue(AppClaimConsts.AutoLogin), out bool autoLogin);

            var passport = await passportRepository.AsQueryable(false)
                .Where(t => t.Account == account)
                .Include(i => i.User)
                .ThenInclude(i => i.Organizations)
                .ThenInclude(i => i.Roles)
                .Include(i => i.User)
                .ThenInclude(i => i.Roles)
                .AsSplitQuery()
                .FirstOrDefaultAsync();

            // 创建JWT Token
            var jwtToken = CreateJwtToken(passport, autoLogin);

            // 设置响应头
            foreach (var item in jwtToken)
            {
                HttpContext.Response.Headers[item.Key] = item.Value;
            }

            return Ok(new NotDataResult { Success = true });
        }

        /// <summary>
        /// 获取当前登录用户
        /// </summary>
        /// <returns></returns>
        [HttpGet("currentUser")]
        [ProducesResponseType(typeof(SimpleResult<UserSession>), 200)]
        public async Task<IActionResult> GetCurrentUserAsync()
        {
            if (int.TryParse(User.FindFirstValue(AppClaimConsts.UserId), out int uid))
            {
                var currUser = await _userRepository.AsQueryable(false).FirstOrDefaultAsync(a => a.Id == uid);
                var nickName = currUser.NickName;
                var avatar = currUser.Avatar;
                // 从当前身份凭据中获取附加数据
                var account = User.FindFirstValue(AppClaimConsts.Account);
                var tenantId = User.FindFirstValue(AppClaimConsts.TenantId);
                var roleList = User.FindFirstValue(AppClaimConsts.RoleIdList)?
                    .Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries) ?? new string[0];

                // 获取当前人员角色模块
                var roleModuleKeys = roleList.Select(s => string.Format(CacheKeyConsts.PermissionModule, s));
                var moduleList = await PermissionCache.GetOrAddManyAsync(roleModuleKeys, async (keys) =>
                {
                    // 构建基本查询
                    var query = _roleRepository.AsQueryable(false)
                        .Where(t => roleList.Contains(t.Code))
                        .Include(i => i.Permissions);

                    // 从key中提取id列表
                    var removeStr = CacheKeyConsts.PermissionModule.Replace("{0}", "");
                    var rowIds = keys.Select(s => s.Replace(removeStr, ""));

                    // 查询并初始化缓存实例
                    var result = new List<KeyValuePair<string, IEnumerable<string>>>();
                    var roles = await query.Where(t => rowIds.Contains(t.Code)).ToListAsync();
                    foreach (var role in roles)
                    {
                        var roleModules = role.Permissions.Select(s => s.Code);

                        if (!string.IsNullOrWhiteSpace(role.PolicyName))
                            roleModules = roleModules.Append(role.PolicyName);

                        var roleCackeKey = string.Format(CacheKeyConsts.PermissionModule, role.Code);
                        result.Add(KeyValuePair.Create(roleCackeKey, roleModules));
                    }

                    return result;
                });

                // 获取当前人员单独授权模块
                var independentModuleKey = string.Format(CacheKeyConsts.PermissionModule, account);
                var independentModuleCodes = await PermissionCache.GetOrAddAsync(independentModuleKey, async () =>
                {
                    // 查询针对人员单独赋予的权限
                    return await _userRepository.AsQueryable(false)
                        .Where(t => t.Id == uid)
                        .Include(i => i.Permissions)
                        .SelectMany(s => s.Permissions)
                        .Select(s => s.Code)
                        .ToListAsync();
                });

                // 构建当前用户的会话信息
                var currentSession = new UserSession { NickName = nickName, Avatar = avatar };
                currentSession.Permissions = moduleList.SelectMany(s => s.Value ?? new List<string>())
                    .Union(independentModuleCodes)
                    .Distinct();

                var response = new SimpleResult<UserSession>();
                response.Success = true;
                response.Data = currentSession;
                return Ok(response);
            }
            return Unauthorized();
        }

        /// <summary>
        /// 获取当前账号配置信息
        /// </summary>
        /// <returns></returns>
        [HttpGet("account/settings")]
        [ProducesResponseType(typeof(SimpleResult<AccountSettingsDto>), 200)]
        public async Task<IActionResult> GetSettingsAsync()
        {
            if (int.TryParse(User.FindFirstValue(AppClaimConsts.UserId), out int uid))
            {
                var entity = await _userRepository.AsQueryable(false)
                    .Where(t => t.Id == uid)
                    .Include(i => i.Roles)
                    .Include(i => i.Organizations)
                    .ThenInclude(i => i.Roles)
                    .Include(i => i.Passports)
                    .AsSplitQuery()
                    .FirstOrDefaultAsync();

                if (entity == null) Unauthorized();

                var currUser = entity.Adapt<AccountSettingsDto>();
                return Ok(new SimpleResult<AccountSettingsDto> { Success = true, Data = currUser });
            }
            return Unauthorized();
        }

        /// <summary>
        /// 更新账号基本信息
        /// </summary>
        /// <param name="modifyDto"></param>
        /// <returns></returns>
        [HttpPost("account/settings")]
        [ProducesResponseType(typeof(NotDataResult), 200)]
        public async Task<IActionResult> UpdateSettingsAsync([FromBody] AccountSettingsModifyDto modifyDto)
        {
            if (int.TryParse(User.FindFirstValue(AppClaimConsts.UserId), out int uid))
            {
                var input = modifyDto.Adapt<UserInfo>();
                var entity = await _userRepository.AsQueryable(false)
                    .Where(t => t.Id == uid)
                    .FirstOrDefaultAsync();

                if (entity == null) Unauthorized();

                var ignoreFields = new string[] { "Id", "CreatedTime", "IsDelete", "HasSeed", "Enable", "EmailConfirmed", "PhoneNumberConfirmed" };
                var update = entity.AsMergeObject(input, ignoreFields);
                update.UpdatedTime = DateTimeOffset.UtcNow;
                await _userRepository.UpdateAsync(update);
                return Ok(new NotDataResult { Success = true });
            }
            return Unauthorized();
        }

        /// <summary>
        /// 获取账号安全设置信息
        /// </summary>
        /// <returns></returns>
        [HttpGet("account/settings/security")]
        [ProducesResponseType(typeof(SimpleResult<AccountSecurityDto>), 200)]
        public IActionResult GetSecuritySettings()
        {
            if (int.TryParse(User.FindFirstValue(AppClaimConsts.UserId), out int uid))
            {
                var model = _userRepository.AsQueryable(false)
                    .Where(t => t.Id == uid)
                    .Include(i => i.Passports)
                    .FirstOrDefault()
                    ?.Adapt<AccountSecurityDto>();

                if (model == null)
                    return Unauthorized();

                return Ok(new SimpleResult<AccountSecurityDto> { Data = model, Success = true });
            }
            return Unauthorized();
        }

        /// <summary>
        /// 修改密码
        /// </summary>
        /// <param name="changeDto"></param>
        /// <returns></returns>
        [HttpPost("account/settings/change-password")]
        [ProducesResponseType(typeof(NotDataResult), 200)]
        public IActionResult UpdataPassword([FromBody] UserPasswordChangeDto changeDto)
        {
            if (int.TryParse(User.FindFirstValue(AppClaimConsts.UserId), out int uid))
            {
                var entity = _userRepository.Where(t => t.Id == uid)
                     .Include(i => i.Passports)
                     .FirstOrDefault();

                // 获取普通账号通行证信息
                var normalPassport = entity?.Passports.FirstOrDefault(t => t.Type == PassportType.Normal);

                if (entity == null || normalPassport == null)
                    return Unauthorized();

                if (normalPassport.Password != changeDto.OldPassword)
                    throw Oops.Oh("原始密码验证未通过,请重新输入.");

                // 更改密码与更新时间
                normalPassport.Password = changeDto.NewPassword;
                normalPassport.UpdatedTime = DateTimeOffset.UtcNow;

                return Ok(new NotDataResult { Success = true });
            }
            return Unauthorized();
        }


        /// <summary>
        /// 账号设置值检查
        /// </summary>
        /// <param name="fieldName"></param>
        /// <param name="fieldValue"></param>
        /// <returns></returns>
        [HttpGet("account/settings/valueCheck")]
        [ProducesResponseType(typeof(NotDataResult), 200)]
        public async Task<IActionResult> CheckIsRedundantAsync([FromServices] UserAppService service, string fieldName, string fieldValue)
        {
            if (int.TryParse(User.FindFirstValue(AppClaimConsts.UserId), out int uid))
            {
                await service.CheckIsRedundantAsync(fieldName, fieldValue, uid);
                return Ok(new NotDataResult { Success = true });
            }
            return Unauthorized();
        }

        /// <summary>
        /// 登出当前用户
        /// </summary>
        /// <returns></returns>
        [HttpPost("logout")]
        [ProducesResponseType(typeof(NotDataResult), 200)]
        public IActionResult LogoutHandle([FromServices] IOuterEventHub eventHub)
        {
            // 私有事件订阅需加入当前用户标识
            var uId = User.FindFirstValue(AppClaimConsts.UserId);
            //eventHub.Get(uId)?.Stop();
            return Ok(new NotDataResult { Success = true });
        }
    }
}
